# coding: utf-8

# -------------------------------------------------------------------------------
# Name:         http_testcase_runner.py
# Description:
# Author:       XiangjunZhao
# EMAIL:        2419352654@qq.com
# Date:         2019/11/18 17:32
# -------------------------------------------------------------------------------
import json
import logging
import retrying
from django.utils import timezone

from apps.HttpAutoTestService.core import utils
from apps.HttpAutoTestService.core.extractor import Extractor
from apps.HttpAutoTestService.core.utils import parse_request_args
from apps.HttpAutoTestService.core.validator import Validator
from apps.HttpAutoTestService.models import TestcaseResult

logger = logging.getLogger(__name__)


class HttpTestcaseRunner(object):

    def __init__(self, http_session=None, http_session_context=None, environment=None, project=None, api=None,
                 testcase=None):
        '''
        实例化用例运行器
        :param http_session:
        :param http_session_context:
        :param environment:运行环境
        :param project:项目
        :param api:接口
        :param testcase:测试用例
        '''
        self.http_session = http_session
        self.http_session_context = http_session_context
        self.environment = environment
        self.project = project
        self.api = api
        self.testcase = testcase

    @retrying.retry(stop_max_attempt_number=3)
    def run(self, testcase_name=None, url=None, headers=None, request_params=None, request_data_type=None,
            request_data=None, expect_result=None, executor=None, is_periodictask=True, loop_count=1):
        '''
        运行测试用命
        :param testcase_name:用例名称
        :param url:请求url
        :param headers:请求头
        :param request_params:查询参数
        :param request_data_type:请求参数类型
        :param request_data:请求参数
        :param expect_result:期望结果
        :param executor:执行者
        :param is_periodictask:是否是定时任务
        :param loop_count:循环次数
        :return:
        '''
        # 在testcase_name, url, headers, request_params, request_data_type, request_data, expect_result传参中，任一传参有值，说明执行的是测试场景中的用例；
        # 在testcase_name, url, headers, request_params, request_data_type, request_data, expect_result传参中，所有传参无值，说明执行的是批量选中的测试用例
        if not any([testcase_name, url, headers, request_params, request_data_type, request_data, expect_result]):
            # 执行的是批量选中的测试用例
            testcase_name = self.testcase.name
            url = self.testcase.url
            headers = self.testcase.headers
            request_params = self.testcase.request_params
            request_data_type = self.testcase.request_data_type
            request_data = self.testcase.request_data
            expect_result = self.testcase.expect_result

        logger.info(f'=============== 执行测试用例:【{testcase_name}】===============')
        method = self.api.method.upper()
        request_kwargs = dict()

        testcase_result_list = []
        testcase_result_mapping = {
            "project_id": self.project.id,
            "project_name": self.project.name,
            "api_id": self.api.id,
            "api_name": self.api.name,
            "testcase_id": self.testcase.id,
            "testcase_name": testcase_name,
            "url": url,
            "method": method,
            "headers": headers,
            "request_data_type": request_data_type,
            "request_data": request_data,
        }
        if not is_periodictask:
            testcase_result_mapping.update({
                'executor_id': executor.id,
                'executor_real_name': executor.real_name,
                'is_periodictask': is_periodictask
            })

        if not (self.environment.is_deleted or self.project.is_deleted or self.api.is_deleted):
            variables_mapping = {**self.http_session_context.session_variables_mapping,
                                 **self.http_session_context.output_variables_mapping}
            logger.info('测试用例【{testcase_name}】执行前，全局参数内容：{variables_mapping}'.format(testcase_name=testcase_name,
                                                                                     variables_mapping=variables_mapping))

            # 解析接口测试用例参数
            request_arg = None  # 标记正在解析的参数
            try:
                request_arg = "请求URL"
                base_url = self.environment.base_url
                url = utils.build_url(base_url=base_url, url=url)
                url = parse_request_args(content=url, variables_mapping=variables_mapping)
                if request_params:
                    request_arg = "请求URL"
                    request_params = parse_request_args(content=request_params,
                                                        variables_mapping=variables_mapping)
                    logger.info('请求查询参数：{request_params}'.format(request_params=request_params))
                    if '?' in url:
                        url = "{url}&{params}".format(url=url.rstrip('&'), params=request_params.lstrip('&'))
                    else:
                        url = "{url}?{params}".format(url=url.rstrip('/'), params=request_params.lstrip('?'))
                logger.info('请求URL：{url}'.format(url=url))

                logger.info('请求方法：{method}'.format(method=method))

                request_arg = "Headers参数"
                headers = parse_request_args(content=headers, variables_mapping=variables_mapping)
                headers = json.loads(headers) if headers else {}
                request_kwargs.update({"headers": headers})
                logger.info('请求头信息：{headers}'.format(headers=headers))

                logger.info('请求体参数类型：{request_data_type}'.format(request_data_type=request_data_type))

                request_arg = "Body参数"
                request_data = parse_request_args(content=request_data, variables_mapping=variables_mapping)
                request_data = json.loads(request_data) if request_data else {}
                logger.info('请求体参数：{request_data}'.format(request_data=request_data))

                if request_data_type == "Json":
                    request_kwargs.update({"json": request_data})
                elif request_data_type == "Form Data":
                    request_kwargs.update({"data": request_data})

                # 期望结果
                request_arg = "期望结果"
                expect_result = parse_request_args(content=expect_result, variables_mapping=variables_mapping)
                expect_result = json.loads(expect_result) if expect_result else {}
                logger.info('期望结果：{expect_result}'.format(expect_result=expect_result))
            except Exception as e:
                testcase_result_mapping.update({
                    "actual_status_code": 0,
                    "actual_response_data": "",
                    "execute_time": timezone.now(),
                    "elapsed_ms": 0,
                    "status": "FAIL",
                    "failure_reason": f"解析接口测试用例参数【{request_arg}】异常，异常原因：{repr(e)}"
                })
                testcase_result = TestcaseResult(**testcase_result_mapping)
                testcase_result_list.append(testcase_result)
                return testcase_result_list

            # 循环执行设定次数
            for i in range(loop_count):
                resp_obj = self.http_session.request(method=method, url=url, **request_kwargs)
                # 获取HttpSession对象request方法返回数据的keys
                response_keys = resp_obj["response"].keys()
                # 获取请求返回数据实际存储的key
                actual_response_key = list(set(response_keys).intersection(['results', 'text', 'content']))[0]
                if isinstance(resp_obj["response"][actual_response_key], str):
                    actual_response_data = resp_obj["response"][actual_response_key]
                else:
                    actual_response_data = json.dumps(resp_obj["response"][actual_response_key], ensure_ascii=False)
                logger.info(f'响应结果：{actual_response_data}')

                if resp_obj["response"]["cookies"]:
                    # 获取响应cookies
                    cookies = resp_obj["response"]["cookies"].get_dict()
                    # 将cookies更新到session中
                    self.http_session.cookies.update(cookies)

                try:
                    if isinstance(expect_result, dict) and expect_result.get("output"):
                        # 创建提取器
                        extractor = Extractor()
                        # 统一格式化测试用例的输出变量output
                        extractor.uniform_output(expect_result.get("output"))
                        # 提取输出变量值
                        output_mapping = extractor.extract_output(resp_obj=resp_obj["response"])
                        # 将提取的输出变量值更新到http_session_context实例的update_output_variables变量中
                        self.http_session_context.update_output_variables(variables_mapping=output_mapping)

                    variables_mapping = {**self.http_session_context.session_variables_mapping,
                                         **self.http_session_context.output_variables_mapping}
                    logger.info(
                        '测试用例【{testcase_name}】执行后，全局参数内容：{variables_mapping}'.format(testcase_name=testcase_name,
                                                                                     variables_mapping=variables_mapping))
                except Exception as e:
                    logger.error(f"提取输出变量值异常，异常原因：{repr(e)}")

                # 创建校验器
                validator = Validator()
                if isinstance(expect_result, dict) and not expect_result.get("validate", None):
                    expect_result["validate"] = {"check": "status_code", "comparator": "eq", "expect": 200}
                # 统一格式化测试用例的校验变量validate
                validator.uniform_validate(expect_result.get("validate"))

                validate_pass, failure_reason = validator.validate(resp_obj=resp_obj["response"])

                testcase_result_mapping.update({
                    "url": url,
                    "headers": json.dumps(headers, ensure_ascii=False),
                    "request_data_type": request_data_type,
                    "request_data": json.dumps(request_data, ensure_ascii=False),
                    "actual_status_code": resp_obj["response"]["status_code"],
                    "actual_response_data": actual_response_data,
                    "execute_time": resp_obj["stat"]["execute_time"],
                    "elapsed_ms": resp_obj["response"]["elapsed"],
                    "status": validate_pass,
                })

                if validate_pass == 'FAIL':
                    logger.warning('测试用例：{name}，测试未通过；原因：{failure_reason}'.format(name=self.testcase.name,
                                                                                  failure_reason=failure_reason))
                    testcase_result_mapping.update({
                        'failure_reason': json.dumps(failure_reason, ensure_ascii=False)
                    })
                testcase_result = TestcaseResult(**testcase_result_mapping)
                testcase_result_list.append(testcase_result)
        return testcase_result_list
